﻿using System;
using System.Reflection;
using System.Windows;
using System.Windows.Input;

namespace Jerry.DroidBuilder.Extensions
{
    public static class CommandExtension
    {
        #region Command

        public static ICommand GetCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(CommandProperty, value);
        }

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CommandExtension), new PropertyMetadata((ICommand)null));

        #endregion

        #region CommandParameter

        public static object GetCommandParameter(DependencyObject obj)
        {
            return (object)obj.GetValue(CommandParameterProperty);
        }

        public static void SetCommandParameter(DependencyObject obj, object value)
        {
            obj.SetValue(CommandParameterProperty, value);
        }

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CommandExtension), new PropertyMetadata(null));

        #endregion

        #region UseArgumentAsParameter

        public static bool GetUseArgumentAsParameter(DependencyObject obj)
        {
            return (bool)obj.GetValue(UseArgumentAsParameterProperty);
        }

        public static void SetUseArgumentAsParameter(DependencyObject obj, bool value)
        {
            obj.SetValue(UseArgumentAsParameterProperty, value);
        }

        public static readonly DependencyProperty UseArgumentAsParameterProperty =
            DependencyProperty.RegisterAttached("UseArgumentAsParameter", typeof(bool), typeof(CommandExtension), new PropertyMetadata(false));

        #endregion

        #region RoutedEvent

        public static RoutedEvent GetRoutedEvent(DependencyObject obj)
        {
            return (RoutedEvent)obj.GetValue(RoutedEventProperty);
        }

        public static void SetRoutedEvent(DependencyObject obj, RoutedEvent value)
        {
            obj.SetValue(RoutedEventProperty, value);
        }

        public static readonly DependencyProperty RoutedEventProperty =
            DependencyProperty.RegisterAttached("RoutedEvent", typeof(RoutedEvent), typeof(CommandExtension), new FrameworkPropertyMetadata(OnRoutedEventChanged));

        /// <summary>
        /// Hooks up a Dynamically created EventHandler (by using the 
        /// <see cref="EventHooker"/> class) that when
        /// run will run the associated ICommand
        /// </summary>
        private static void OnRoutedEventChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            RoutedEvent routedEvent = e.NewValue as RoutedEvent;

            if (null != routedEvent && source is UIElement)
            {
                EventHooker eventHooker = new EventHooker();
                eventHooker.EventSource = source;

                ((UIElement)source).AddHandler(routedEvent, eventHooker.GetEventHandler(routedEvent));
            }
        }

        #endregion

        #region Internal Class: EventHooker

        /// <summary>
        /// Contains the event that is hooked into the source RoutedEvent
        /// that was specified to run the ICommand
        /// </summary>
        private sealed class EventHooker
        {
            #region Public Methods/Properties

            /// <summary>
            /// The DependencyObject, that holds a binding to the actual
            /// ICommand to execute
            /// </summary>
            public DependencyObject EventSource { get; set; }

            /// <summary>
            /// Creates a Dynamic EventHandler that will be run the ICommand
            /// when the user specified RoutedEvent fires
            /// </summary>
            public Delegate GetEventHandler(RoutedEvent routedEvent)
            {
                if (null == routedEvent)
                {
                    throw new ArgumentNullException("routedEvent");
                }

                return Delegate.CreateDelegate(routedEvent.HandlerType, this, GetType().GetMethod("OnEventRaised", BindingFlags.NonPublic | BindingFlags.Instance));
            }

            /// <summary>
            /// Creates a Dynamic EventHandler that will be run the ICommand
            /// when the user specified event fires
            /// </summary>
            public Delegate GetEventHandler(EventInfo eventInfo)
            {
                if (eventInfo == null)
                {
                    throw new ArgumentNullException("eventInfo");
                }

                if (eventInfo.EventHandlerType == null)
                {
                    throw new ArgumentException("EventHandlerType is null");
                }

                // No throw exception when create the following delegate, if has some problem, just leave it null.
                Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, GetType().GetMethod("OnEventRaised", BindingFlags.NonPublic | BindingFlags.Instance), false);

                if (null == handler)
                {
                    // When the event is like IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e), need use this following delegate.
                    handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, GetType().GetMethod("OnPropertyChanged", BindingFlags.NonPublic | BindingFlags.Instance), false);
                }

                return handler;
            }

            #endregion

            #region Private Methods

            /// <summary>
            /// Runs the ICommand when the requested RoutedEvent fires
            /// </summary>
            private void OnEventRaised(object sender, EventArgs e)
            {
                var command = GetCommand(EventSource);
                var useArgument = GetUseArgumentAsParameter(EventSource);
                var commandParameter = GetCommandParameter(EventSource);

                if (null != command)
                {
                    if (useArgument)
                    {
                        command.Execute(e);
                    }
                    else
                    {
                        command.Execute(commandParameter);
                    }
                }
            }

            #endregion
        }

        #endregion
    }
}
